package cn.ronghuanet.user.service.impl;

import cn.ronghuanet.auth.service.ILoginService;
import cn.ronghuanet.base.constants.BaseConstans;
import cn.ronghuanet.base.constants.WxConstants;
import cn.ronghuanet.base.dto.RegisterSendSmsDTO;
import cn.ronghuanet.base.exception.ResponseCode;
import cn.ronghuanet.base.exception.RonghuaAssert;
import cn.ronghuanet.base.util.HttpUtil;
import cn.ronghuanet.base.util.MD5Utils;
import cn.ronghuanet.user.domain.Wxuser;
import cn.ronghuanet.user.dto.RegisterNoticeEmailDTO;
import cn.ronghuanet.user.dto.RegisterNoticeSmsDTO;
import cn.ronghuanet.user.dto.UserBindWechatDTO;
import cn.ronghuanet.user.dto.UserPhoneRegisterDTO;
import cn.ronghuanet.user.domain.Logininfo;
import cn.ronghuanet.user.domain.User;
import cn.ronghuanet.user.mapper.LogininfoMapper;
import cn.ronghuanet.user.mapper.UserMapper;
import cn.ronghuanet.user.mapper.WxuserMapper;
import cn.ronghuanet.user.rabbitmq.RegisterNoticeRabbitmqConfig;
import cn.ronghuanet.user.service.IUserService;
import cn.ronghuanet.base.service.impl.BaseServiceImpl;
import com.alibaba.fastjson.JSONObject;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author xiong
 * @since 2023-07-07
 */
@Service
public class UserServiceImpl extends BaseServiceImpl<User> implements IUserService {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private LogininfoMapper logininfoMapper;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private WxuserMapper wxuserMapper;

    @Autowired
    private ILoginService loginService;

    @Transactional
    @Override
    public void phoneRegister(UserPhoneRegisterDTO dto) {
        // 1 参数校验
        // 1.1 非空校验  jsr303
        // 1.2 两次密码是否一致
        RonghuaAssert.isEq(dto.getPassword(),dto.getConfirmPassword(), ResponseCode.RESPONSE_CODE_40011);

        // 1.3 手机验证码是否正确
        String redisKey = BaseConstans.REGISTER_SMS_SEND_KEY + dto.getMobile();
        Object obj = redisTemplate.opsForValue().get(redisKey);
        // 如果redis没有值,说明已经过期
        RonghuaAssert.isNotNull(obj,ResponseCode.RESPONSE_CODE_40020);
        // 手机验证码的value是由两部分组成的
        String smsCode = obj.toString().split(":")[0];
        RonghuaAssert.isEq(dto.getSmsCode(),smsCode,ResponseCode.RESPONSE_CODE_40021);

        // 1.4 账号是否已被注册
        User user = userMapper.loadByUsername(dto.getUsername());
        RonghuaAssert.isNull(user,ResponseCode.RESPONSE_CODE_40022);

        // 1.5 校验邮箱是否已被注册
        user = userMapper.loadByEmail(dto.getEmail());
        RonghuaAssert.isNull(user,ResponseCode.RESPONSE_CODE_40023);

        // 2 保存登录信息到logininfo表中,要先存logininfo再存user,否则user表会操作两次
        Logininfo logininfo = new Logininfo();
        logininfo.setUsername(dto.getUsername());
        logininfo.setPhone(dto.getMobile());
        logininfo.setEmail(dto.getEmail());
        logininfo.setType(1);
        logininfo.setDisable(1);
        // 用户注册的时候,存储密码时需要对密码做加盐加密的处理
        String salt = UUID.randomUUID().toString();
        String password = MD5Utils.encrypByMd5("ronghua"+dto.getPassword()+"1234"+salt);
        logininfo.setSalt(salt);
        logininfo.setPassword(password);
        logininfoMapper.insert(logininfo);

        // 3 保存用户信息到user表中
        user = new User();
        BeanUtils.copyProperties(logininfo,user);
        // logininfo已经做了新增,现在它的里面是有ID的
        user.setId(null);
        user.setState(2);   // 已激活
        user.setAge(dto.getAge());
        user.setCreatetime(new Date());
        user.setLogininfoId(logininfo.getId());
        userMapper.insert(user);

        // 4 用户注册成功,需要发送邮件和短信告知用户
        // 4.1 发送邮件队列需要的消息内容到邮件的邮件队列中
        // 第三个参数,要放入消息队列的内容  收件人邮箱 主题 内容
        RegisterNoticeEmailDTO emailDTO = new RegisterNoticeEmailDTO(dto.getEmail(),"蓉车宝用户注册成功通知",
                "恭喜你,成功注册蓉车宝,新人福利等你领取!");
        rabbitTemplate.convertAndSend(RegisterNoticeRabbitmqConfig.EXCHANGE_TOPICS_INFORM,"inform.email",emailDTO);

        // 4.2 发送短信队列需要的消息内容到邮件的短信队列中   消息内容:手机号  内容
        RegisterNoticeSmsDTO smsDTO = new RegisterNoticeSmsDTO(dto.getMobile(),"恭喜你,成功注册蓉车宝,新人福利等你领取!");
        rabbitTemplate.convertAndSend(RegisterNoticeRabbitmqConfig.EXCHANGE_TOPICS_INFORM,"inform.sms",smsDTO);
    }

    @Override
    public Map<String, Object> bindWechat(UserBindWechatDTO dto) {
        // 1 参数校验
        // 1.1 参数非空校验
        // 1.2 验证码是否匹配
        // 1.2.1 从redis中获取验证码
        Object obj = redisTemplate.opsForValue().get(BaseConstans.BIND_SMS_SEND_KEY + dto.getPhone());
        RonghuaAssert.isNotNull(obj,ResponseCode.RESPONSE_CODE_40020);
        // 1.2.2 比对redis和前端传入的验证码
        // 从redis中取出来的值, 验证码:发送时间
        String smsCode = obj.toString().split(":")[0];
        RonghuaAssert.isEq(dto.getVerifyCode(),smsCode,ResponseCode.RESPONSE_CODE_40021);

        // 2 通过accessToken&openid调用微信接口,获取微信用户信息
        String getUserUrl = WxConstants.GET_USER_URL.replace("ACCESS_TOKEN",dto.getAccessToken())
                                        .replace("OPENID",dto.getOpenid());
        String userStr = HttpUtil.httpGet(getUserUrl);
        // 将获取到的微信用户信息转为WxUser对象
        Wxuser wxuser = str2WxUser(userStr);

        // 3 通过手机号到t_user表中获取用户数据
        User user = userMapper.loadByPhone(dto.getPhone());
        Logininfo logininfo = null;
        // 4 如果手机号用户存在,判断该用户是否绑定了微信用户
        if(Objects.nonNull(user)){
            //根据userId去t_wxuser表中查询
            Wxuser wxuser1 = wxuserMapper.loadByUserId(user.getId());
            // 4.1 如果绑定过微信用户,返回错误信息,该用户已绑定过微信用户,请直接使用正确的微信扫码登录
            RonghuaAssert.isNull(wxuser1,ResponseCode.RESPONSE_CODE_40026);

            // 4.2 如果没有绑定过微信用户,直接将该用户与微信用户做绑定,免密登录成功
            // 4.2.3 做免密登录,查询logininfo
            logininfo = logininfoMapper.loadByUserId(user.getId());
        }else{
            // 5 如果手机号用户不存在
            // 5.1 新建logininfo
            logininfo = getLogininfo(dto.getPhone());
            logininfoMapper.insert(logininfo);

            // 5.2 新建user
            user = logininfo2User(logininfo);
            userMapper.insert(user);
        }
        // 5.3 将user与微信用户做绑定
        wxuser.setUserId(user.getId());
        wxuserMapper.insert(wxuser);

        // 5.4 免密登录
        Map<String, Object> map = loginService.loginSuccess(logininfo);
        return map;
    }

    private User logininfo2User(Logininfo logininfo) {
        User user = new User();
        BeanUtils.copyProperties(logininfo,user);
        user.setId(null);
        user.setLogininfoId(logininfo.getId());
        user.setState(2);
        user.setCreatetime(new Date());
        return user;
    }

    private Logininfo getLogininfo(String phone) {
        Logininfo logininfo = new Logininfo();
        logininfo.setUsername("rh_"+UUID.randomUUID().toString().replaceAll("-",""));
        logininfo.setPhone(phone);
        String salt = UUID.randomUUID().toString();
        // 一般在这种情况下,会设置一个默认密码,比如123456,或者手机号后6号之类的,但是不管用哪一种设置密码,最终都需要发送短信告知用户
        String password = MD5Utils.encrypByMd5("ronghua"+"123456"+"1234"+salt);
        logininfo.setSalt(salt);
        logininfo.setPassword(password);
        logininfo.setType(1);
        logininfo.setDisable(1);
        return logininfo;
    }

    /**
     * 将微信返回的微信信息转换为我们自己的wxuser对象
     * @param userStr
     * @return
     */
    private Wxuser str2WxUser(String userStr) {
        JSONObject userObj = JSONObject.parseObject(userStr);
        Wxuser wxuser = new Wxuser();
        wxuser.setOpenid(userObj.getString("openid"));
        wxuser.setNickname(userObj.getString("nickname"));
        wxuser.setSex(userObj.getInteger("sex"));
        wxuser.setAddress(userObj.getString("country")+userObj.getString("province")+userObj.getString("city"));
        wxuser.setHeadimgurl(userObj.getString("headimgurl"));
        wxuser.setUnionid(userObj.getString("unionid"));
        return wxuser;
    }
}
